using System;
using System.Collections.Generic;
using System.Text;
using Bule.Malbilden.Data;

namespace Bule.Malbilden.Domain
{
    public class NutritionData : IPersistable
    {
        private const double kcalPerGramFat = 9;
        private const double kcalPerGramProtein = 4;
        private const double kcalPerGramCarbohydrates = 4;
        private const double kcalPerGramFiber = 0;

        public enum MacroNutrient
        {
            Protein,
            Carbohydrates,
            UnsaturatedFat,
            SaturatedFat,
            Fiber
        }

        public enum MicroNutrient
        {
            VitaminA,
            VitaminB1,
            VitaminB2,
            VitaminB3,
            VitaminB6,
            VitaminB12,
            VitaminC,
            VitaminD,
            VitaminE,
            FolicAcid,
            Calcium,
            Iron,
            Sodium,
            Potassium,
            Phosphorus,
            Magnesium,
            Zinc,
            Selenium
        }

        private IDictionary<MicroNutrient, double> microNutrients = new Dictionary<MicroNutrient, double>();
        private IDictionary<MacroNutrient, double> macroNutrients = new Dictionary<MacroNutrient, double>();

        private IDictionary<MacroNutrient, double> energyDensities = new Dictionary<MacroNutrient, double>();

        public NutritionData()
            : this(0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) { }

        public NutritionData(double kcal, double protein, double carbs,
            double unsaturatedFat, double saturatedFat, double fiber,
            double vitaminA, double vitaminB1, double vitaminB2,
            double vitaminB3, double vitaminB6, double vitaminB12,
            double vitaminC, double vitaminD, double vitaminE,
            double folicAcid, double calcium, double iron, double sodium,
            double potassium, double phosphorus, double magnesium, double zinc,
            double selenium)
        {
            Kcal = kcal;

            macroNutrients[MacroNutrient.Protein] = protein;
            macroNutrients[MacroNutrient.Carbohydrates] = carbs;
            macroNutrients[MacroNutrient.UnsaturatedFat] = unsaturatedFat;
            macroNutrients[MacroNutrient.SaturatedFat] = saturatedFat;
            macroNutrients[MacroNutrient.Fiber] = fiber;

            microNutrients[MicroNutrient.VitaminA] = vitaminA;
            microNutrients[MicroNutrient.VitaminB1] = vitaminB1;
            microNutrients[MicroNutrient.VitaminB2] = vitaminB2;
            microNutrients[MicroNutrient.VitaminB3] = vitaminB3;
            microNutrients[MicroNutrient.VitaminB6] = vitaminB6;
            microNutrients[MicroNutrient.VitaminB12] = vitaminB12;
            microNutrients[MicroNutrient.VitaminC] = vitaminC;
            microNutrients[MicroNutrient.VitaminD] = vitaminD;
            microNutrients[MicroNutrient.VitaminE] = vitaminE;
            microNutrients[MicroNutrient.FolicAcid] = folicAcid;
            microNutrients[MicroNutrient.Calcium] = calcium;
            microNutrients[MicroNutrient.Iron] = iron;
            microNutrients[MicroNutrient.Sodium] = sodium;
            microNutrients[MicroNutrient.Potassium] = potassium;
            microNutrients[MicroNutrient.Phosphorus] = phosphorus;
            microNutrients[MicroNutrient.Magnesium] = magnesium;
            microNutrients[MicroNutrient.Zinc] = zinc;
            microNutrients[MicroNutrient.Selenium] = selenium;

            energyDensities[MacroNutrient.Carbohydrates] = kcalPerGramCarbohydrates;
            energyDensities[MacroNutrient.Protein] = kcalPerGramProtein;
            energyDensities[MacroNutrient.UnsaturatedFat] = kcalPerGramFat;
            energyDensities[MacroNutrient.SaturatedFat] = kcalPerGramFat;
            energyDensities[MacroNutrient.Fiber] = kcalPerGramFiber;
        }

        public virtual int Id { get; set; }
        public virtual double Kcal { get; set; }

        public virtual double Protein
        {
            get { return macroNutrients[MacroNutrient.Protein]; }
            set { macroNutrients[MacroNutrient.Protein] = value; }
        }
        public virtual double Carbohydrates
        {
            get { return macroNutrients[MacroNutrient.Carbohydrates]; }
            set { macroNutrients[MacroNutrient.Carbohydrates] = value; }
        }
        public virtual double UnsaturatedFat
        {
            get { return macroNutrients[MacroNutrient.UnsaturatedFat]; }
            set { macroNutrients[MacroNutrient.UnsaturatedFat] = value; }
        }
        public virtual double SaturatedFat
        {
            get { return macroNutrients[MacroNutrient.SaturatedFat]; }
            set { macroNutrients[MacroNutrient.SaturatedFat] = value; }
        }
        public virtual double Fiber
        {
            get { return macroNutrients[MacroNutrient.Fiber]; }
            set { macroNutrients[MacroNutrient.Fiber] = value; }
        }
        public virtual double VitaminA
        {
            get { return microNutrients[MicroNutrient.VitaminA]; }
            set { microNutrients[MicroNutrient.VitaminA] = value; }
        }
        public virtual double VitaminB1
        {
            get { return microNutrients[MicroNutrient.VitaminB1]; }
            set { microNutrients[MicroNutrient.VitaminB1] = value; }
        }
        public virtual double VitaminB2
        {
            get { return microNutrients[MicroNutrient.VitaminB2]; }
            set { microNutrients[MicroNutrient.VitaminB2] = value; }
        }
        public virtual double VitaminB3
        {
            get { return microNutrients[MicroNutrient.VitaminB3]; }
            set { microNutrients[MicroNutrient.VitaminB3] = value; }
        }
        public virtual double VitaminB6
        {
            get { return microNutrients[MicroNutrient.VitaminB6]; }
            set { microNutrients[MicroNutrient.VitaminB6] = value; }
        }
        public virtual double VitaminB12
        {
            get { return microNutrients[MicroNutrient.VitaminB12]; }
            set { microNutrients[MicroNutrient.VitaminB12] = value; }
        }
        public virtual double VitaminC
        {
            get { return microNutrients[MicroNutrient.VitaminC]; }
            set { microNutrients[MicroNutrient.VitaminC] = value; }
        }
        public virtual double VitaminD
        {
            get { return microNutrients[MicroNutrient.VitaminD]; }
            set { microNutrients[MicroNutrient.VitaminD] = value; }
        }
        public virtual double VitaminE
        {
            get { return microNutrients[MicroNutrient.VitaminE]; }
            set { microNutrients[MicroNutrient.VitaminE] = value; }
        }
        public virtual double FolicAcid
        {
            get { return microNutrients[MicroNutrient.FolicAcid]; }
            set { microNutrients[MicroNutrient.FolicAcid] = value; }
        }
        public virtual double Calcium
        {
            get { return microNutrients[MicroNutrient.Calcium]; }
            set { microNutrients[MicroNutrient.Calcium] = value; }
        }
        public virtual double Iron
        {
            get { return microNutrients[MicroNutrient.Iron]; }
            set { microNutrients[MicroNutrient.Iron] = value; }
        }
        public virtual double Sodium
        {
            get { return microNutrients[MicroNutrient.Sodium]; }
            set { microNutrients[MicroNutrient.Sodium] = value; }
        }
        public virtual double Potassium
        {
            get { return microNutrients[MicroNutrient.Potassium]; }
            set { microNutrients[MicroNutrient.Potassium] = value; }
        }
        public virtual double Phosphorus
        {
            get { return microNutrients[MicroNutrient.Phosphorus]; }
            set { microNutrients[MicroNutrient.Phosphorus] = value; }
        }
        public virtual double Magnesium
        {
            get { return microNutrients[MicroNutrient.Magnesium]; }
            set { microNutrients[MicroNutrient.Magnesium] = value; }
        }
        public virtual double Zinc
        {
            get { return microNutrients[MicroNutrient.Zinc]; }
            set { microNutrients[MicroNutrient.Zinc] = value; }
        }
        public virtual double Selenium
        {
            get { return microNutrients[MicroNutrient.Selenium]; }
            set { microNutrients[MicroNutrient.Selenium] = value; }
        }

        // Calculated properties

        public virtual double Fat
        {
            get { return UnsaturatedFat + SaturatedFat; }
        }

        public virtual double GetEnergyPercentage(MacroNutrient nutrient)
        {
            return macroNutrients[nutrient] * energyDensities[nutrient] / Kcal * 100;
        }


        public virtual double GetMicroNutrient(MicroNutrient nutrient)
        {
            return microNutrients[nutrient];
        }

        public virtual double GetMacroNutrient(MacroNutrient nutrient)
        {
            return macroNutrients[nutrient];
        }

        public virtual void SetMicroNutrient(MicroNutrient nutrient, double value)
        {
            microNutrients[nutrient] = value;
        }

        public virtual void SetMacroNutrient(MacroNutrient nutrient, double value)
        {
            macroNutrients[nutrient] = value;
        }

        public static double KcalPerGramFat { get { return kcalPerGramFat; } }
        public static double KcalPerGramProtein { get { return kcalPerGramProtein; } }
        public static double KcalPerGramCarbohydrates { get { return kcalPerGramCarbohydrates; } }

        public static NutritionData operator +(NutritionData left, NutritionData right)
        {
            NutritionData result = new NutritionData();
            result.Kcal = left.Kcal + right.Kcal;

            foreach (MacroNutrient nutrient in Enum.GetValues(typeof(MacroNutrient)))
                result.SetMacroNutrient(nutrient, left.GetMacroNutrient(nutrient) + right.GetMacroNutrient(nutrient));

            foreach (MicroNutrient nutrient in Enum.GetValues(typeof(MicroNutrient)))
                result.SetMicroNutrient(nutrient, left.GetMicroNutrient(nutrient) + right.GetMicroNutrient(nutrient));

            return result;
        }

        public static NutritionData operator *(NutritionData left, double right)
        {
            NutritionData result = new NutritionData();
            result.Kcal = left.Kcal * right;

            foreach (MacroNutrient nutrient in Enum.GetValues(typeof(MacroNutrient)))
                result.SetMacroNutrient(nutrient, left.GetMacroNutrient(nutrient) * right);

            foreach (MicroNutrient nutrient in Enum.GetValues(typeof(MicroNutrient)))
                result.SetMicroNutrient(nutrient, left.GetMicroNutrient(nutrient) * right);

            return result;
        }

        public static NutritionData operator /(NutritionData left, double right)
        {
            NutritionData result = new NutritionData();
            result.Kcal = left.Kcal / right;

            foreach (MacroNutrient nutrient in Enum.GetValues(typeof(MacroNutrient)))
                result.SetMacroNutrient(nutrient, left.GetMacroNutrient(nutrient) / right);

            foreach (MicroNutrient nutrient in Enum.GetValues(typeof(MicroNutrient)))
                result.SetMicroNutrient(nutrient, left.GetMicroNutrient(nutrient) / right);

            return result;
        }

        public static bool operator ==(NutritionData left, NutritionData right)
        {
            bool areEqual = left.Kcal == right.Kcal;

            foreach (MacroNutrient nutrient in Enum.GetValues(typeof(MacroNutrient)))
                areEqual = areEqual && (Math.Round(left.GetMacroNutrient(nutrient), 2) == Math.Round(right.GetMacroNutrient(nutrient), 2));

            foreach (MicroNutrient nutrient in Enum.GetValues(typeof(MicroNutrient)))
                areEqual = areEqual && (Math.Round(left.GetMicroNutrient(nutrient), 2) == Math.Round(right.GetMicroNutrient(nutrient), 2));

            return areEqual;
        }

        public static bool operator !=(NutritionData left, NutritionData right)
        {
            return !(left == right);
        }

        public override bool Equals(object obj)
        {
            return this == (NutritionData)obj;
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public override string ToString()
        {
            return "Protein: " + Protein + ", Carbs: " + Carbohydrates + ", Fat: " + UnsaturatedFat + SaturatedFat;
        }
    }
}
